#include "tinyprintf.h"

#define ZEROPAD (1<<0) /* Pad with zero */
#define SIGN (1<<1) /* Unsigned/signed long */
#define PLUS (1<<2) /* Show plus */
#define SPACE (1<<3) /* Spacer */
#define LEFT (1<<4) /* Left justified */
#define HEX_PREP (1<<5) /* 0x */
#define UPPERCASE (1<<6) /* 'ABCDEF' */
#define is_digit(c) ((c) >= '0' && (c) <= '9')

static char *lower_digits = "0123456789abcdefghijklmnopqrstuvwxyz";
static char *upper_digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";


static size_t strnlen(const char *s, size_t count);

static size_t strnlen(const char *s, size_t count)
{
	const char *sc;
	for (sc = s; *sc != '\0' && count--; ++sc)
		;
	return sc - s;
}
static int ee_skip_atoi(const char **s)
{
	int i = 0;
	while (is_digit(**s))
		i = i * 10 + *((*s)++) - '0';
	return i;
}
static char *ee_number(char *str, long num, int base, int size, int precision,
		int type)
{
	char c, sign, tmp[66];
	char *dig = lower_digits;
	int i;
	if (type & UPPERCASE)
		dig = upper_digits;
	if (type & LEFT)
		type &= ~ZEROPAD;
	if (base < 2 || base > 36)
		return 0;
	c = (type & ZEROPAD) ? '0' : ' ';
	sign = 0;
	if (type & SIGN) {
		if (num < 0) {
			sign = '-';
			num = -num;
			size--;
		} else if (type & PLUS) {
			sign = '+';
			size--;
		} else if (type & SPACE) {
			sign = ' ';
			size--;
		}
	}
	if (type & HEX_PREP) {
		if (base == 16)
			size -= 2;
		else if (base == 8)
			size--;
	}
	i = 0;
	if (num == 0)
		tmp[i++] = '0';
	else {
		while (num != 0) {
			tmp[i++] = dig[((unsigned long) num) % (unsigned) base];
			num = ((unsigned long) num) / (unsigned) base;
		}
	}
	if (i > precision)
		precision = i;
	size -= precision;
	if (!(type & (ZEROPAD | LEFT)))
		while (size-- > 0)
			*str++ = ' ';
	if (sign)
		*str++ = sign;
	if (type & HEX_PREP) {
		if (base == 8)
			*str++ = '0';
		else if (base == 16) {
			*str++ = '0';
			*str++ = lower_digits[33];
		}
	}
	if (!(type & LEFT))
		while (size-- > 0)
			*str++ = c;
	while (i < precision--)
		*str++ = '0';
	while (i-- > 0)
		*str++ = tmp[i];
	while (size-- > 0)
		*str++ = ' ';
	return str;
}
static char *eaddr(char *str, unsigned char *addr, int size, int precision,
		int type) {
	char tmp[24];
	char *dig = lower_digits;
	int i, len;
	if (type & UPPERCASE)
		dig = upper_digits;
	len = 0;
	for (i = 0; i < 6; i++) {
		if (i != 0)
			tmp[len++] = ':';
		tmp[len++] = dig[addr[i] >> 4];
		tmp[len++] = dig[addr[i] & 0x0F];
	}
	if (!(type & LEFT))
		while (len < size--)
			*str++ = ' ';
	for (i = 0; i < len; ++i)
		*str++ = tmp[i];
	while (len < size--)
		*str++ = ' ';
	return str;
}
static char *iaddr(char *str, unsigned char *addr, int size, int precision,
		int type) {
	char tmp[24];
	int i, n, len;
	len = 0;
	for (i = 0; i < 4; i++) {
		if (i != 0)
			tmp[len++] = '.';
		n = addr[i];
		if (n == 0)
			tmp[len++] = lower_digits[0];
		else {
			if (n >= 100) {
				tmp[len++] = lower_digits[n / 100];
				n = n % 100;
				tmp[len++] = lower_digits[n / 10];
				n = n % 10;
			} else if (n >= 10) {
				tmp[len++] = lower_digits[n / 10];
				n = n % 10;
			}
			tmp[len++] = lower_digits[n];
		}
	}
	if (!(type & LEFT))
		while (len < size--)
			*str++ = ' ';
	for (i = 0; i < len; ++i)
		*str++ = tmp[i];
	while (len < size--)
		*str++ = ' ';
	return str;
}
static int ee_vsprintf(char *buf, const char *fmt, va_list args) {
	int len;
	unsigned long num;
	int i, base;
	char *str;
	char *s;
	int flags; // Flags to number()
	int field_width; // Width of output field
	int precision; // Min. # of digits for integers; max number of chars for from string
	int qualifier; // 'h', 'l', or 'L' for integer fields
	for (str = buf; *fmt; fmt++) {
		if (*fmt != '%') {
			*str++ = *fmt;
			continue;
		}
// Process flags
		flags = 0;
		repeat: fmt++; // This also skips first '%'
		switch (*fmt) {
		case '-':
			flags |= LEFT;
			goto repeat;
		case '+':
			flags |= PLUS;
			goto repeat;
		case ' ':
			flags |= SPACE;
			goto repeat;
		case '#':
			flags |= HEX_PREP;
			goto repeat;
		case '0':
			flags |= ZEROPAD;
			goto repeat;
		}
// Get field width
		field_width = -1;
		if (is_digit(*fmt))
			field_width = ee_skip_atoi(&fmt);
		else if (*fmt == '*') {
			fmt++;
			field_width = va_arg(args, int);
			if (field_width < 0) {
				field_width = -field_width;
				flags |= LEFT;
			}
		}
// Get the precision
		precision = -1;
		if (*fmt == '.') {
			++fmt;
			if (is_digit(*fmt))
				precision = ee_skip_atoi(&fmt);
			else if (*fmt == '*') {
				++fmt;
				precision = va_arg(args, int);
			}
			if (precision < 0)
				precision = 0;
		}
// Get the conversion qualifier
		qualifier = -1;
		if (*fmt == 'l' || *fmt == 'L') {
			qualifier = *fmt;
			fmt++;
		}
// Default base
		base = 10;
		switch (*fmt) {
		case 'c':
			if (!(flags & LEFT))
				while (--field_width > 0)
					*str++ = ' ';
			*str++ = (unsigned char) va_arg(args, int);
			while (--field_width > 0)
				*str++ = ' ';
			continue;
		case 's':
			s = va_arg(args, char *);
			if (!s)
				s = "<NULL>";
			len = strnlen(s, precision);
			if (!(flags & LEFT))
				while (len < field_width--)
					*str++ = ' ';
			for (i = 0; i < len; ++i)
				*str++ = *s++;
			while (len < field_width--)
				*str++ = ' ';
			continue;
		case 'p':
			if (field_width == -1) {
				field_width = 2 * sizeof(void *);
				flags |= ZEROPAD;
			}
			str = ee_number(str, (unsigned int) va_arg(args, void *), 16,
					field_width, precision, flags);
			continue;
		case 'A':
			flags |= UPPERCASE;
		case 'a':
			if (qualifier == 'l')
				str = eaddr(str, va_arg(args, unsigned char *), field_width,
						precision, flags);
			else
				str = iaddr(str, va_arg(args, unsigned char *), field_width,
						precision, flags);
			continue;
// Integer number formats - set up the flags and "break"
		case 'o':
			base = 8;
			break;
		case 'X':
			flags |= UPPERCASE;
		case 'x':
			base = 16;
			break;
		case 'd':
		case 'i':
			flags |= SIGN;
		case 'u':
			break;
		default:
			if (*fmt != '%')
				*str++ = '%';
			if (*fmt)
				*str++ = *fmt;
			else
				--fmt;
			continue;
		}
		if (qualifier == 'l')
			num = va_arg(args, unsigned long);
		else if (flags & SIGN)
			num = va_arg(args, int);
		else
			num = va_arg(args, unsigned int);
		str = ee_number(str, num, base, field_width, precision, flags);
	}
	*str = '\0';
	return str - buf;
}
int ee_sprintf(char *buf, const char *fmt, ...) {
	char *p;
	va_list args;
	int n = 0;
	va_start(args, fmt);
	ee_vsprintf(buf, fmt, args);
	va_end(args);

	return n;
}
